# 知识点
# 什么是张量
#   张量是 pytorch 运算的基本单元，pytorch 运算时是 张量 与 张量 进行运算，张量中存储的其实是多维数据

# 什么是 numpy 数组
#   一组序列化的数据集合，其实就是多维列表，numpy 自动化的优化了多维数据的算子（算子指的是加减乘除等运算方法）
# 安装
# pip install numpy

# 重点:
# 1. 张量创建
# 2. 张量访问
# 3. 张量形状修改
# 1. 数组创建
# 2. 数组访问
# 3. 数组形状修改
# 4. 矩阵运算

# 导包
import numpy as np

# 手动创建张量值 np.array
arr = np.array([1, 2, 3])
arr = np.array([
    [1, 2, 3],
    [4, 5, 6]
])
arr = np.array([
    [
        [1, 2, 3],
        [4, 5, 6]
    ],
    [
        [4, 5, 6],
        [7, 8, 9]
    ]
])
print(arr)

# 查看形状
print(arr.shape)

arr = np.array([
    [2, 2, 3],
    [5, 5, 6]
])
print(arr.shape)

# 创建空张量
# 形状的长度就是数据的维度
print(len(arr.shape))

# 创建空数组: 空数组中的数据并未初始化，采用的是内存中的原始数据
arr = np.empty((4, 5))
print(arr)
print(arr.shape)

# 创建 0 张量
# 创建 0 数组: 使用 0 初始化的数字
arr = np.zeros((4, 5))
print(arr)

# 创建 1 数组: 使用 1 初始化的数字
arr = np.ones((4, 5))
print(arr)

# 查询类型
print(type(arr))
# 查询 numpy 数组的数据类型
print(arr.dtype)

# 张量内的数据类型
# 指定数据类型


# empty  vs  zero
# empty: 创建张量但不初始化
# zero: 创建张量并初始化为 0

arr = np.ones((2, 3), dtype=np.uint8)
print(arr)

# 转换数据类型
print(arr.astype(np.int64))
print(arr.astype(np.int64).dtype)

# 创建 1 张量


# 随机张量
# 随机数组
# 设置随机种子


# print(torch.rand(2, 3))  # 0~1
# print(torch.randn(2, 3))  # 满足正态分布的随机数，均值为 0，方差为 1
# print(torch.randint(-3, 5, (2, 3)))  # 生成 -3~5 之间的随机整数
# print(torch.normal(mean=-1, std=2, size=(2, 3)))  # 生成正态分布的随机数，均值为 -1，方差为 2

# 通过以下 api 创建形状和 t 相同的张量
# t = torch.rand(2, 3, 4)
#
# print(torch.empty_like(t))
# print(torch.zeros_like(t))
# print(torch.ones_like(t))
# print(torch.rand_like(t))
# print(torch.randn_like(t))
# print(torch.randint_like(t, -1, 1))
np.random.seed(100)

print(np.random.rand(2, 3))  # 0~1
print(np.random.randn(2, 3))  # 满足正态分布的随机数，均值为 0，方差为 1
print(np.random.randint(-3, 6, (2, 3)))  # 生成 -3~5 之间的随机整数
print(np.random.normal(5, 2, (2, 3)))  # 生成正态分布的随机数，均值为 -1，方差为 2
print(np.random.normal(5, 2, (2, 3)))  # 生成正态分布的随机数，均值为 5，标准差为 2

# 通过以下 api 创建形状和 arr 相同的数组
arr = np.random.rand(2, 3, 4)

print(np.arange(10, 20))
print(np.linspace(1, 10, 10))
print(np.linspace(2, 5, 10))

# 访问张量成员
# 访问数组成员
arr = np.arange(12).reshape(3, 4)
print(arr)
print(arr.shape)

print(arr.shape)
# 索引和切片
print(arr[2, 1])
print(type(arr[2, 1]))

# 获取数组对象内的原始数据
print(arr[2, 1].item())
print(type(arr[2, 1].item()))
print(arr[0])

# 第一个维度从索引为 0 取到 2，满足左闭右开
print(arr[0:2])
print(arr[0:2, 1:3])
# 切片省略头的时候，代表从头开始
print(arr[:2])
# 切片省略尾的时候，代表从第一个参数起取到尾
print(arr[1:])
# 切片省略头和尾的时候，代表从头取到尾
print(arr[:, 2])
# 切片中的第二个冒号后定义的是步长，步长代表隔几个数取值
print(arr[:, 0:4:2])
print(arr[:, ::2])

# 修改形状
# 注意，参数的乘积，必须等于原形状各维度的乘积
arr = np.arange(12)

# reshape
# t = torch.arange(12).reshape(3, 4)
# print(t)
print(arr.reshape(3, 4).shape)

# -1: 可以自动计算某个维度下的长度

print(arr.reshape(2, -1).shape)

# transpose: 改变维度顺序
arr = np.arange(24).reshape(2, 3, 4)
print(arr.shape)
# 参数代表维度的索引
print(arr.transpose(2, 0, 1).shape)

# 二维数据的矩阵转置可以直接用 t.T

# 二维数据的矩阵转置可以直接用 arr.T 代表转置后的矩阵
arr = np.arange(12).reshape(3, 4)
print(arr)
print(arr.T)
print(arr.T.shape)

# 扩展维度
# np.expand_dims

arr = np.arange(12).reshape(3, 4)
# 参数代表要添加维度的索引
print(np.expand_dims(arr, axis=0).shape)
print(np.expand_dims(arr, axis=1).shape)
print(np.expand_dims(arr, axis=2).shape)

# 收缩维度
# np.sqeeze()
arr = np.ones((1, 2, 1, 3))
print(arr.shape)
# 将所有长度为1的维度去掉
print(np.squeeze(arr).shape)
print(np.squeeze(arr, axis=2).shape)
# 当指定维度长度不是 1 的时候，无法消除维度
# print(np.squeeze(arr, axis=1).shape)

print('==' * 50)

# 和标量运算
arr = np.arange(12).reshape(3, 4)
print(arr + 1)
print(arr - 1)
print(arr * 2)
print(arr / 2)
print(arr % 2)
print(arr ** 2)

# 具有相同形状的张量可以进行逐位的数学运算


# 张量广播
# 什么是张量广播?
# PyTorch 中的张量广播（broadcasting）是一种机制，用于在执行元素级操作时自动扩展具有不同形状的张量，使它们具有兼容的形状。这使你能够执行一些操作，而无需显式地扩展张量的形状。

a = np.array([
    [1, 2],
    [3, 4]
])
b = np.array([
    [2, 2],
    [1, 0]
])
print(a + b)

# 使用掩码取值或赋值
arr = np.arange(12).reshape(3, 4)
print(arr)

# 创建掩码
mask = np.array([
    [True, False, True, False],
    [False, True, False, True],
    [True, False, True, False]
])

# 掩码取值赋值

# bool 掩码为 True 的位置会被取值
# print(arr[mask])
# arr[mask] = 0
# print(arr)

# 通过条件创建掩码
mask = arr % 2 == 0
print(mask)
# 取反
mask = ~mask
print(mask)

# 统计整数出现的次数

arr = np.random.randint(0, 3, (3, 4))
print(arr)
# np.bincount 接收的数据维度必须是一维数据
# 输出中，每个数字的索引，代表统计的是那个数字，例如输出 [2, 4, 6]
# 2: 代表 0 出现的次数
# 4: 代表 1 出现的次数
# 6: 代表 2 出现的次数
count = np.bincount(arr.reshape(-1))
print(count)

# 非零数

# 返回值为非零数据的索引
# 返回值代表的是每个维度下的索引
idx = np.nonzero(arr)
print(idx)

# 非零数数量
count = np.count_nonzero(arr)
print(count)

# 张量中最大值

# 数组中最大值 最小值
arr = np.random.randint(0, 3, (3, 4))
print(arr)
print(np.max(arr, axis=0))
print(np.max(arr, axis=1))
print(np.min(arr, axis=0))
print(np.min(arr, axis=1))

# 均值
# 求指定dim维度上的均值
print(np.mean(arr, axis=0))
print(np.mean(arr, axis=1))

# 方差: 每个数减去平均数的平方
print(np.var(arr, axis=0))

# 方差


# 标准差


# 矩阵乘法
# A 是 3x2 那么 B 就必须是 2x3 否则不能相乘
# 相乘结果形状是 3x3; 取 A 的行数和 B 的列数
# 矩阵乘法算法:
# A 的第一行点乘 B 的第一列并求和
# A 的第二行点乘 B 的第一列并求和
# A 的第三行点乘 B 的第一列并求和
# 前三次运算结果放入新矩阵的第一列
# 再次用 A 的每行和 B 的第二列点乘并求和；结果放入新矩阵第二列
# 再次用 A 的每行和 B 的第三列点乘并求和；结果放入新矩阵第二列

# 批量矩阵乘法
# 结论:
# 1. A B 形状的右上到左下对角线相等则可以相乘
# 2. A B 相乘结果 C 的形状为 A B 形状的左上和右下
# 3. 矩阵相乘不具备交换性

# 标准差: 方差的平方根
print(np.std(arr, axis=0))

# 克隆
print(np.copy(arr))

print('==' * 50)

# 拼接

a = np.random.randint(0, 3, (3, 4))
print(a)
b = np.random.randint(0, 3, (3, 4))
print(b)
# axis 对应维度连接一起
c = np.concat((a, b), axis=0)
c = np.concat((a, b), axis=1)
print(c)
print(c.shape)

# 堆叠
c = np.stack((a, b), axis=0)
print(c)
print(np.stack((a, b), axis=0).shape)
print(np.stack((a, b), axis=1).shape)
print(np.stack((a, b), axis=2).shape)
